package com.jintian.smart.kernel.autoconfigure.nacos;

import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.annotation.NacosConfigurationProperties;
import com.alibaba.nacos.spring.context.properties.config.NacosConfigurationPropertiesBinder;
import com.alibaba.nacos.spring.core.env.NacosPropertySource;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;

/**
 * 解决com.alibaba.boot.nacos.config.binder.NacosBootConfigurationPropertiesBinder的Spring兼容性问题
 * 废弃原因：私库 nacos-spring-context-2.1.0 已修复此问题
 */
public class NacosBootConfigurationPropertiesBinder extends NacosConfigurationPropertiesBinder {
    private StandardEnvironment environment = new StandardEnvironment();
    private ConfigurableListableBeanFactory beanFactory;

    public NacosBootConfigurationPropertiesBinder(ConfigurableApplicationContext applicationContext) {
        super(applicationContext);
        this.beanFactory = applicationContext.getBeanFactory();
    }

    @Override
    protected void doBind(Object bean, String beanName, String dataId, String groupId, String configType,
                          NacosConfigurationProperties properties, String content, ConfigService configService) {
        synchronized (this) {
            String name = "nacos-bootstrap-" + beanName;
            NacosPropertySource propertySource = new NacosPropertySource(name, dataId, groupId, content, configType);
            environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, propertySource);
            Binder binder = Binder.get(environment);
            ResolvableType type = getBeanType(bean, beanName);
            Bindable<?> target = Bindable.of(type).withExistingValue(bean);
            binder.bind(properties.prefix(), target);
            publishBoundEvent(bean, beanName, dataId, groupId, properties, content, configService);
            publishMetadataEvent(bean, beanName, dataId, groupId, properties);
            environment.getPropertySources().remove(name);
        }
    }

    private MethodMetadata getFactoryMethodMetadata(String beanName) {
        BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(beanName);
        if (beanDefinition instanceof AnnotatedBeanDefinition) {
            return ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata();
        }
        return null;
    }

    private ResolvableType getBeanType(Object bean, String beanName) {
        /**
         * <pre>
         * private ResolvableType getBeanType(Object bean, String beanName) {
         * 	Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
         * 	if (factoryMethod != null) {
         * 		return ResolvableType.forMethodReturnType(factoryMethod);
         *    }
         * 	return ResolvableType.forClass(bean.getClass());
         * }
         * </pre>
         */
        MethodMetadata mmd = this.getFactoryMethodMetadata(beanName);
        if (mmd != null) {
            Method method = ReflectionUtils.findMethod(this.beanFactory.getType(mmd.getDeclaringClassName()),
                    mmd.getMethodName());
            ResolvableType.forMethodReturnType(method);
        }
        return ResolvableType.forClass(bean.getClass());

    }

}
